package com.yhao.floatwindow;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.os.Build;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;

/**
 * Created by yhao on 2017/12/22.
 * https://github.com/yhaolpz
 */

public class IFloatWindowImpl extends IFloatWindow {

    private FloatWindow.Builder mBuilder;
    private FloatView mFloatView;
    private FloatLifecycle mFloatLifecycle;
    private boolean isShow;
    private boolean isShowing;
    private boolean isHiding;
    private boolean firstTime = true;
    private ValueAnimator mAnimator;
    private TimeInterpolator mDecelerateInterpolator;
    private float downX;
    private float downY;
    private float upX;
    private float upY;
    private boolean mClick = false;
    private int mSlop;
    //消失view的x和y坐标
    private int xCancelOffset, yCancelOffset;
    //屏幕右下角的x\y坐标
    private int xCoordinate, yCoordinate;
    //屏幕高度，总视图的高度
    private int screenWidth, parentHeight;

    IFloatWindowImpl(FloatWindow.Builder builder) {
        screenWidth = Util.getScreenWidth(builder.mApplicationContext) - builder.mWidth;
        parentHeight = builder.parentHeight - builder.mHeight;
        mBuilder = builder;
        if (mBuilder.mMoveType == MoveType.fixed) {
            //7.0 以下采用自定义 toast, 7.1 及以上引导用户申请权限
            //这里的代码不执行，因为没有用MoveType.fixed
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
                mFloatView = new FloatPhone(builder.mApplicationContext, mBuilder.mPermissionListener);
            } else {
                mFloatView = new FloatToast(builder.mApplicationContext);
            }
        } else {
            //都是执行的这里的代码，记住
            mFloatView = new FloatPhone(builder.mApplicationContext, mBuilder.mPermissionListener);
            initTouchEvent();
        }
        mFloatView.setSize(mBuilder.mWidth, mBuilder.mHeight);
        mFloatView.setGravity(mBuilder.gravity, mBuilder.xOffset, mBuilder.yOffset);
        mFloatView.setView(mBuilder.mView);
        if (mBuilder.showAnimator != null) {
            mBuilder.showAnimator.addUpdateListener(updateListener);
        }
        mFloatLifecycle = new FloatLifecycle(mBuilder.mApplicationContext,
                                             mBuilder.currentActivityCount,
                                             mBuilder.mShow,
                                             mBuilder.mActivities,
                                             new LifecycleListener() {
                                                 @Override
                                                 public void onShow() {
                                                     show();
                                                 }

                                                 @Override
                                                 public void onHide() {
                                                     hide();
                                                 }

                                                 @Override
                                                 public void onBackToDesktop() {
                                                     if (!mBuilder.mDesktopShow) {
                                                         hide();
                                                     }
                                                     if (mBuilder.mViewStateListener != null) {
                                                         mBuilder.mViewStateListener.onBackToDesktop();
                                                     }
                                                 }
                                             });
    }

    public FloatWindow.Builder getBuilder() {
        return mBuilder;
    }

    @Override
    public int[] getOffset() {
        int[] array = new int[2];
        array[0] = mBuilder.xOffset;
        array[1] = mBuilder.yOffset;
        return array;
    }

    @Override
    public void init() {
        if (firstTime) {
            mFloatView.init();
            firstTime = false;
            getView().setVisibility(View.INVISIBLE);
        }
    }

    @Override
    public void show() {
        LogUtil.e(mBuilder.mTag);
        if (firstTime) {
            mFloatView.init();
            firstTime = false;
            getView().setVisibility(View.INVISIBLE);
        }
        if (mBuilder.showAnimator != null) {
            if (isShow || isShowing) return;
            mBuilder.showAnimator.removeAllListeners();
            mBuilder.showAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    isShow = true;
                    isShowing = false;
                    if (mBuilder.mViewStateListener != null) {
                        mBuilder.mViewStateListener.onShow();
                    }
                    mBuilder.showAnimator.removeAllListeners();
                }
            });
            if (isHiding) {
                mBuilder.showAnimator.reverse();
                isHiding = false;
            } else {
                mBuilder.showAnimator.start();
            }
            isShowing = true;
            getView().setVisibility(View.VISIBLE);
        } else {
            if (isShow) return;
            getView().setVisibility(View.VISIBLE);
            isShow = true;
            if (mBuilder.mViewStateListener != null) {
                mBuilder.mViewStateListener.onShow();
            }
        }
    }

    @Override
    public void hide() {
        if (firstTime) {
            return;
        }
        if (mBuilder.showAnimator != null) {
            if (isHiding) return;
            mBuilder.showAnimator.removeAllListeners();
            mBuilder.showAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    getView().setVisibility(View.INVISIBLE);
                    isShow = false;
                    isHiding = false;
                    if (mBuilder.mViewStateListener != null) {
                        mBuilder.mViewStateListener.onHide();
                    }
                    mBuilder.showAnimator.removeAllListeners();
                }
            });
            if (isShowing) {
                mBuilder.showAnimator.reverse();
                isShowing = false;
            } else {
                mBuilder.showAnimator.reverse();
            }
            isHiding = true;
        } else {
            if (!isShow) {
                return;
            }
            getView().setVisibility(View.INVISIBLE);
            isShow = false;
            if (mBuilder.mViewStateListener != null) {
                mBuilder.mViewStateListener.onHide();
            }
        }
    }

    private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Object objX = animation.getAnimatedValue("x");
            Object objY = animation.getAnimatedValue("y");
            if (objX == null && objY == null) {
                throw new RuntimeException("Animator中x和y属性值至少需指定一个");
            }
            int x = objX == null ? mBuilder.xOffset : (int) animation.getAnimatedValue("x");
            int y = objY == null ? mBuilder.yOffset : (int) animation.getAnimatedValue("y");
            mFloatView.updateXY(x, y);
        }
    };

    @Override
    public boolean isShowing() {
        return isShow;
    }

    @Override
    public void dismiss() {
        mFloatView.dismiss();
        isShow = false;
        if (mBuilder.mViewStateListener != null) {
            mBuilder.mViewStateListener.onDismiss();
        }
    }

    @Override
    public void updateX(int x) {
        checkMoveType();
        mBuilder.xOffset = x;
        mFloatView.updateX(x);
    }

    @Override
    public void updateY(int y) {
        checkMoveType();
        mBuilder.yOffset = y;
        mFloatView.updateY(y);
    }

    @Override
    public void updateX(int screenType, float ratio) {
        checkMoveType();
        mBuilder.xOffset = (int) ((screenType == Screen.width ? Util.getScreenWidth(mBuilder.mApplicationContext) : Util.getScreenHeight(mBuilder.mApplicationContext)) * ratio);
        mFloatView.updateX(mBuilder.xOffset);
    }

    @Override
    public void updateY(int screenType, float ratio) {
        checkMoveType();
        mBuilder.yOffset = (int) ((screenType == Screen.width ? Util.getScreenWidth(mBuilder.mApplicationContext) : Util.getScreenHeight(mBuilder.mApplicationContext)) * ratio);
        mFloatView.updateY(mBuilder.yOffset);

    }

    @Override
    public int getX() {
        return mFloatView.getX();
    }

    @Override
    public int getY() {
        return mFloatView.getY();
    }

    @Override
    public View getView() {
        mSlop = ViewConfiguration.get(mBuilder.mApplicationContext).getScaledTouchSlop();
        return mBuilder.mView;
    }

    private void checkMoveType() {
        if (mBuilder.mMoveType == MoveType.fixed) {
            throw new IllegalArgumentException("FloatWindow of this tag is not allowed to move!");
        }
    }

    private void initTouchEvent() {
        switch (mBuilder.mMoveType) {
            case MoveType.inactive:
                break;
            default:
                getView().setOnTouchListener(new View.OnTouchListener() {
                    float lastX, lastY, changeX, changeY;
                    int newX, newY;

                    @SuppressLint("ClickableViewAccessibility")
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {

                        switch (event.getAction()) {
                            case MotionEvent.ACTION_DOWN:
                                downX = lastX = event.getRawX();
                                downY = lastY = event.getRawY();
                                cancelAnimator();
                                if (mBuilder.mViewStateListener != null) {
                                    mBuilder.mViewStateListener.onPositionUpdate(MotionEvent.ACTION_DOWN, mBuilder.xOffset, mBuilder.yOffset);
                                }
                                break;
                            case MotionEvent.ACTION_MOVE:
                                float rawX = event.getRawX();
                                float rawY = event.getRawY();
                                changeX = rawX - lastX;
                                changeY = rawY - lastY;
                                newX = (int) (mFloatView.getX() + changeX);
                                newY = (int) (mFloatView.getY() + changeY);

                                if (newX <= 0) {
                                    newX = 0;
                                }
                                if (newX >= screenWidth) {
                                    newX = screenWidth;
                                }
                                if (newY <= 0) {
                                    newY = 0;
                                }
                                if (newY >= parentHeight) {
                                    newY = parentHeight;
                                }
                                mFloatView.updateXY(newX, newY);
                                if (mBuilder.mViewStateListener != null) {
                                    mBuilder.mViewStateListener.onPositionUpdate(MotionEvent.ACTION_MOVE, newX, newY);
                                }
                                lastX = rawX;
                                lastY = rawY;
                                break;
                            case MotionEvent.ACTION_UP:
                                upX = event.getRawX();
                                upY = event.getRawY();
                                mClick = (Math.abs(upX - downX) > mSlop) || (Math.abs(upY - downY) > mSlop);
                                switch (mBuilder.mMoveType) {
                                    case MoveType.slide:
                                        int startX = mFloatView.getX();
                                        int endX = (startX * 2 + v.getWidth() > Util.getScreenWidth(mBuilder.mApplicationContext)) ? Util.getScreenWidth(
                                                mBuilder.mApplicationContext) - v.getWidth() - mBuilder.mSlideRightMargin : mBuilder.mSlideLeftMargin;
                                        mBuilder.xOffset = endX;
                                        mBuilder.yOffset = newY;
                                        if (mBuilder.mViewStateListener != null) {
                                            mBuilder.mViewStateListener.onPositionUpdate(MotionEvent.ACTION_UP, startX, (int) upY);
                                        }
                                        mAnimator = ObjectAnimator.ofInt(startX, endX);
                                        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                                            @Override
                                            public void onAnimationUpdate(ValueAnimator animation) {
                                                int x = (int) animation.getAnimatedValue();
                                                mFloatView.updateX(x);

                                            }
                                        });
                                        startAnimator();
                                        break;
                                    case MoveType.back:
                                        PropertyValuesHolder pvhX = PropertyValuesHolder.ofInt("x", mFloatView.getX(), mBuilder.xOffset);
                                        PropertyValuesHolder pvhY = PropertyValuesHolder.ofInt("y", mFloatView.getY(), mBuilder.yOffset);
                                        mAnimator = ObjectAnimator.ofPropertyValuesHolder(pvhX, pvhY);
                                        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                                            @Override
                                            public void onAnimationUpdate(ValueAnimator animation) {
                                                int x = (int) animation.getAnimatedValue("x");
                                                int y = (int) animation.getAnimatedValue("y");
                                                mFloatView.updateXY(x, y);
                                                if (mBuilder.mViewStateListener != null) {
                                                    mBuilder.mViewStateListener.onPositionUpdate(MotionEvent.ACTION_UP, x, y);
                                                }
                                            }
                                        });
                                        startAnimator();
                                        break;
                                    default:
                                        break;
                                }
                                break;
                            default:
                                break;
                        }
                        return mClick;
                    }

                });
        }
    }

    private void startAnimator() {
        if (mBuilder.mInterpolator == null) {
            if (mDecelerateInterpolator == null) {
                mDecelerateInterpolator = new DecelerateInterpolator();
            }
            mBuilder.mInterpolator = mDecelerateInterpolator;
        }
        mAnimator.setInterpolator(mBuilder.mInterpolator);
        mAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mAnimator.removeAllUpdateListeners();
                mAnimator.removeAllListeners();
                mAnimator = null;
                if (mBuilder.mViewStateListener != null) {
                    mBuilder.mViewStateListener.onMoveAnimEnd(mBuilder.xOffset, mBuilder.yOffset);
                }
            }
        });
        mAnimator.setDuration(mBuilder.mDuration).start();
        if (mBuilder.mViewStateListener != null) {
            mBuilder.mViewStateListener.onMoveAnimStart();
        }
    }

    private void cancelAnimator() {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
    }

}
